﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.Storage;
using OverStore.Scoping;
using OverStore.Runtime.Logger;
using OverStore.Exceptions;
using System.Globalization;
using OverStore.Runtime.ObjectRepository.IdentifierProvider;

namespace OverStore.Runtime.ObjectRepository.Reader
{
    /// <summary>
    /// Iterates throught set of instances of <typeparamref name="TItem"/> class read 
    /// from data storage.
    /// </summary>
    /// <typeparam name="TItem">The type of the item.</typeparam>
    /// <typeparam name="TIdentifier">The type of the item identifier.</typeparam>
    public sealed class ResultSetEnumerator<TItem, TIdentifier> : IEnumerator<TItem>, IEquatable<ResultSetEnumerator<TItem, TIdentifier>>
        where TItem : class
    {
        private readonly ICommand ReadCommand;
        private readonly IDataStorage DataStorage;
        private readonly IIdentifierProvider<TIdentifier> IdentifierProvider;
        private readonly IRecord Parameters;
        private readonly bool UseResultsetForConstruction;
        private readonly IPersistenceServiceProvider Provider;
        private readonly ILogger Logger;
        private IEnumerator<TypedRecord> _Reader;

        /// <summary>
        /// Initializes a new instance of the <see cref="ResultSetEnumerator{TItem, TIdentifier}"/> struct.
        /// </summary>
        /// <param name="readCommand">The read command.</param>
        /// <param name="storage">The data storage uses for read data.</param>
        /// <param name="parameters">The parameters.</param>
        /// <param name="identifierProvider">The identifier provider used tp get instance identifier from current data.</param>
        /// <param name="logger">The logger.</param>
        /// <param name="useResultSetForConstruction">if set to <c>true</c> use resultset for construction object; otherwise, query instance data separately.</param>
        /// <param name="provider">The provider.</param>
        public ResultSetEnumerator(
                    ICommand readCommand,
                    IDataStorage storage,
                    IRecord parameters,
                    IIdentifierProvider<TIdentifier> identifierProvider,
                    ILogger logger,
                    bool useResultSetForConstruction,
                    IPersistenceServiceProvider provider)
        {
            Debug.Assert(readCommand != null, "Read Command is null");
            Debug.Assert(parameters != null, "Parameters is null");
            Debug.Assert(identifierProvider != null, "Identifier provider is null or empty");
            Debug.Assert(provider != null, "Provider is null");
            Debug.Assert(storage != null, "Data Storage is null.");
            ReadCommand = readCommand;
            DataStorage = storage;
            Parameters = parameters;
            IdentifierProvider = identifierProvider;
            UseResultsetForConstruction = useResultSetForConstruction;
            Provider = provider;
            Logger = logger;
            _Reader = null;
        }

        /// <summary>
        /// Gets the current.
        /// </summary>
        /// <result>The current.</result>
        public TItem Current
        {
            get
            {
                if (_Reader == null)
                    throw new InvalidOperationException("The enumerator is positioned before the first element.");
                try
                {
                    TIdentifier id = IdentifierProvider.GetIdentifier(_Reader.Current);
                    if (UseResultsetForConstruction)
                        return Provider.GetInstance<TItem, TIdentifier>(id, _Reader.Current);
                    else
                        return Provider.GetInstance<TItem, TIdentifier>(id);
                }
                catch (PersistingRuntimeException ex)
                {
                    throw new PersistenceOperationFailedException(
                                String.Format(
                                    CultureInfo.InvariantCulture, 
                                    "Object Reader error: accessing current item of object reader for instance of type {0} failed.", 
                                    typeof(TItem)),
                                ex);
                }
            }
        }

        /// <summary>
        /// Gets the current.
        /// </summary>
        /// <result>The current.</result>
        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(ResultSetEnumerator<TItem, TIdentifier> left, ResultSetEnumerator<TItem, TIdentifier> right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(ResultSetEnumerator<TItem, TIdentifier> left, ResultSetEnumerator<TItem, TIdentifier> right)
        {
            return !(left == right);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (_Reader != null)
                _Reader.Dispose();
        }

        /// <summary>
        /// Advances the enumerator to the next element of the collection.
        /// </summary>
        /// <returns>
        /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
        /// </returns>
        /// <exception cref="T:System.InvalidOperationException">
        /// The collection was modified after the enumerator was created.
        /// </exception>
        public bool MoveNext()
        {
            try
            {
                if (_Reader == null)
                {
                    _Reader = DataStorage.ReadAll(ReadCommand, Parameters).GetEnumerator();
                }
                return _Reader.MoveNext();
            }
            catch (PersistingRuntimeException ex)
            {
                throw new PersistenceOperationFailedException(
                    String.Format(
                        CultureInfo.InvariantCulture, 
                        "Object Reader error: reading next instance of type {0} failed.", 
                        typeof(TItem)),
                    ex);
            }
        }

        /// <summary>
        /// Sets the enumerator to its initial position, which is before the first element in the collection.
        /// </summary>
        /// <exception cref="T:System.InvalidOperationException">
        /// The collection was modified after the enumerator was created.
        /// </exception>
        public void Reset()
        {
            try
            {
                _Reader.Reset();
            }
            catch (PersistingRuntimeException ex)
            {
                throw new PersistenceOperationFailedException(
                    String.Format(
                        CultureInfo.InvariantCulture, 
                        "Object Reader error: resetting object reader of instances of type {0} failed.", 
                        typeof(TItem)),
                    ex);
            }
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///     <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null))
                return false;
            if (!ReferenceEquals(obj.GetType(), typeof(ResultSetEnumerator<TItem, TIdentifier>)))
                return false;
            var en = (ResultSetEnumerator<TItem, TIdentifier>)obj;
            return Equals(en);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        public bool Equals(ResultSetEnumerator<TItem, TIdentifier> other)
        {
            return ReferenceEquals(_Reader, other._Reader) &&
                   ReferenceEquals(ReadCommand, other.ReadCommand) &&
                   ReferenceEquals(DataStorage, other.DataStorage) &&
                   ReferenceEquals(IdentifierProvider, other.IdentifierProvider) &&
                   ReferenceEquals(Parameters, other.Parameters) &&
                   UseResultsetForConstruction == other.UseResultsetForConstruction;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return (_Reader == null ? 0 : _Reader.GetHashCode()) +
                   ReadCommand.GetHashCode() +
                   DataStorage.GetHashCode() +
                   IdentifierProvider.GetHashCode() +
                   Parameters.GetHashCode() +
                   (UseResultsetForConstruction ? 1 : 0);
        }
    }

    /// <summary>
    /// Allows to obtain instances of <see cref="ResultSetEnumerator{TItem, TIdentifier}"/> for 
    /// iterate throught set of items read from data storage.
    /// </summary>
    /// <typeparam name="TItem">The type of the item.</typeparam>
    /// <typeparam name="TIdentifier">The type of the item identifier.</typeparam>
    public class ResultSetEnumerable<TItem, TIdentifier> : IEnumerable<TItem> where TItem : class
    {
        private readonly ICommand ReadCommand;
        private readonly IDataStorage Storage;
        private readonly IIdentifierProvider<TIdentifier> IdentifierProvider;
        private readonly IRecord Parameters;
        private readonly bool UseResultsetForConstruction;
        private readonly IPersistenceServiceProvider Provider;
        private readonly ILogger Logger;

        /// <summary>
        /// Initializes a new instance of the <see cref="ResultSetEnumerable{TItem, TIdentifier}"/> class.
        /// </summary>
        /// <param name="readCommand">The read command.</param>
        /// <param name="storage">The storage uses for read data.</param>
        /// <param name="parameters">The parameters.</param>
        /// <param name="identifierProvider">The identifier provider.</param>
        /// <param name="logger">The logger.</param>
        /// <param name="useResultSetForConstruction">if set to <c>true</c>, instance uses data read from storage to construct instances;
        /// otherwise data for instance construction reads separately via default data gateway.</param>
        /// <param name="provider">The persistence service provider.</param>
        public ResultSetEnumerable(
                    ICommand readCommand,
                    IDataStorage storage,
                    IRecord parameters,
                    IIdentifierProvider<TIdentifier> identifierProvider,
                    ILogger logger,
                    bool useResultSetForConstruction,
                    IPersistenceServiceProvider provider)
        {
            Debug.Assert(readCommand != null, "Read Command is null");
            Debug.Assert(parameters != null, "Parameters is null");
            Debug.Assert(identifierProvider != null, "Identifier Provider is null.");
            Debug.Assert(provider != null, "Provider is null");
            Debug.Assert(storage != null, "Storage is null.");
            ReadCommand = readCommand;
            Parameters = parameters;
            IdentifierProvider = identifierProvider;
            Provider = provider;
            UseResultsetForConstruction = useResultSetForConstruction;
            Storage = storage;
            Logger = logger;
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<TItem> GetEnumerator()
        {
            if (Storage.Disposed)
                throw new ObjectDisposedException("DataStorage", "Opening object reader failed because persistence session was disposed.");
            return new ResultSetEnumerator<TItem, TIdentifier>(ReadCommand, Storage, Parameters, IdentifierProvider, Logger, UseResultsetForConstruction, Provider);
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
